#include "StdAfx.h"
#include <windows.h>
#include "FileCacheManager.h"
#include "ServerProxyUtil/MD5HashHelper.h"

SVR_PROXY_NAMESPACE_BEGIN

FileCacheManager::FileCacheManager(const long& maxFolderCacheSize, 
								   const long& maxMemCacheSize, 
								   const long& maxCacheEntrySize)
:m_maxFolderCacheSize(maxFolderCacheSize),
m_maxMemCacheSize(maxMemCacheSize),
m_maxCacheEntrySize(maxCacheEntrySize)
{
	m_checkHash = true;
	m_currentCacheSize = 0;
	m_currentFolderSize = 0;
	pthread_rwlock_init(&m_rwlock, NULL);
}

FileCacheManager::~FileCacheManager(void)
{
	DestroyCache();
	pthread_rwlock_destroy(&m_rwlock);
}

bool FileCacheManager::InitCache(const char* cacheBaseDir, bool checkHash)
{
	if(cacheBaseDir == NULL)
		return false;

	m_cacheBaseDir = cacheBaseDir;
	if(m_cacheBaseDir.at(m_cacheBaseDir.length() - 1) != '\\')
		m_cacheBaseDir += '\\';
	m_checkHash = checkHash;

	HANDLE hFile = NULL;
	hFile = CreateFile(cacheBaseDir, GENERIC_READ, FILE_SHARE_READ, NULL,
						OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

	if(INVALID_HANDLE_VALUE == hFile && !CreateDirectory(cacheBaseDir, NULL))
		return false;
	CloseHandle(hFile);

	bool ret = false;
	pthread_rwlock_wrlock(&m_rwlock);
	ret = FillCache(m_cacheBaseDir);
	pthread_rwlock_unlock(&m_rwlock);
	return ret;
}

bool FileCacheManager::DestroyCache()
{
	pthread_rwlock_wrlock(&m_rwlock);
	tCacheMap::iterator Iter = m_cacheMap.begin();
	for(; Iter != m_cacheMap.end(); Iter++)
	{
		tCacheEntry* entry = (tCacheEntry*)Iter->second;
		entry->buffer.release();
		SAFE_DELETE(entry);
	}
	m_cacheMap.clear();
	pthread_rwlock_unlock(&m_rwlock);
	return true;
}

bool FileCacheManager::GetCache(const char* name, tDataBuffer* buffer, bool checkDisk, bool checkHash)
{
	if(name == NULL)
		return false;

	pthread_rwlock_rdlock(&m_rwlock);
	tCacheEntry* entry = NULL;

	tCacheMap::iterator Iter = m_cacheMap.find(name);
	if(Iter == m_cacheMap.end())
	{
		if(!checkDisk && buffer == NULL)
		{
			pthread_rwlock_unlock(&m_rwlock);
			return false;
		}

		entry = new tCacheEntry;
		memset(entry, 0, sizeof(tCacheEntry));
		pthread_rwlock_unlock(&m_rwlock);
		
		if(!AddCacheEntry(m_cacheBaseDir + name, name, entry, checkHash))
		{
			entry->buffer.release();
			delete entry;
			return false;
		}
		pthread_rwlock_rdlock(&m_rwlock);
	}
	else
		entry = Iter->second;

	if(buffer != NULL)
	{
		buffer->data = entry->buffer.data;
		buffer->size = entry->buffer.size;
	}
	entry->hit_count++;
	pthread_rwlock_unlock(&m_rwlock);
	return true;
}

bool FileCacheManager::AddCache(const char* name, const tDataBuffer& buffer, bool writeToDisk, bool checkHash)
{
	if(name == NULL)
		return false;
	
	if(checkHash && !MD5HashHelper::CheckDataBufferHash(buffer, name))
		return false;

	tDataBuffer temp;
	if(GetCache(name, &temp, true, checkHash))
	{
		if(temp.size == buffer.size &&
			memcmp(temp.data, buffer.data, buffer.size) == 0)
			return true;
		else
			RemoveCache(name);
	}

	pthread_rwlock_wrlock(&m_rwlock);
	if(writeToDisk)
	{
		string path = m_cacheBaseDir + name;
		HANDLE hFile = CreateFile(path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
									CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
		if(INVALID_HANDLE_VALUE == hFile)
		{
			pthread_rwlock_unlock(&m_rwlock);
			return false;
		}
		DWORD dw = -1;
		WriteFile(hFile, buffer.data, buffer.size, &dw, NULL);
		FlushFileBuffers(hFile);
		CloseHandle(hFile);
	}
	tCacheEntry* entry = new tCacheEntry;
	memset(entry, 0, sizeof(tCacheEntry));
	entry->buffer.data = new byte[buffer.size];
	memcpy(entry->buffer.data, buffer.data, buffer.size);
	entry->buffer.size = buffer.size;

	m_cacheMap[name] = entry;
	pthread_rwlock_unlock(&m_rwlock);
	return true;
}

bool FileCacheManager::RemoveCache(const char* name)
{
	pthread_rwlock_wrlock(&m_rwlock);
	tCacheMap::iterator iter = m_cacheMap.find(name);
	if(iter == m_cacheMap.end())
	{
		pthread_rwlock_unlock(&m_rwlock);
		return false;
	}
	iter->second->hit_count--;
	if(iter->second->hit_count <= 0)
	{
		m_currentCacheSize -= iter->second->buffer.size;
		iter->second->buffer.release();
		SAFE_DELETE(iter->second);
		m_cacheMap.erase(iter);
	}
	pthread_rwlock_unlock(&m_rwlock);
	return true;
}

bool FileCacheManager::FillCache(const string& dirPath)
{
	WIN32_FIND_DATA FindFileData;
	HANDLE hFind;
	
	if(dirPath.length() <= 0)
		return false;

	string searchPath = dirPath;

	if(searchPath.at(searchPath.length() - 1) != '\\')
		searchPath += '\\';
	string rootPath(searchPath);

	searchPath += "*.*";

	hFind = FindFirstFile(searchPath.c_str(), &FindFileData);
	if(hFind == INVALID_HANDLE_VALUE) 
		return false;

	while(FindNextFile(hFind, &FindFileData))
	{
		if(m_currentCacheSize > m_maxMemCacheSize)
			break;

		if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			if(strcmp(FindFileData.cFileName, ".") == 0
				|| strcmp(FindFileData.cFileName, "..") == 0)
				continue;
		
			FillCache(rootPath + FindFileData.cFileName);
			continue;
		}

		/*if(strstr(FindFileData.cFileName, ".jpg") <= 0 &&
			strstr(FindFileData.cFileName, ".png") <= 0 &&
			strstr(FindFileData.cFileName, ".xml") <= 0)
			continue;*/

		if(m_cacheMap.find(FindFileData.cFileName) != m_cacheMap.end())
			continue;

		tCacheEntry* entry = new tCacheEntry;
		memset(entry, 0, sizeof(tCacheEntry));
		if(!AddCacheEntry(rootPath + FindFileData.cFileName, FindFileData.cFileName, entry, m_checkHash))
		{
			entry->buffer.release();
			SAFE_DELETE(entry);
		}
	}
	FindClose(hFind);
	return true;
}

bool FileCacheManager::AddCacheEntry(const string& path, const string& name, tCacheEntry* entry, bool checkHash)
{
	HANDLE hFile = NULL;
	hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
						OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

	if(INVALID_HANDLE_VALUE == hFile)
		return false;

	DWORD dwSize = -1;
	DWORD dwHigh = -1;
	dwSize = GetFileSize(hFile, &dwHigh);
	if(dwHigh > m_maxCacheEntrySize)
	{
		CloseHandle(hFile);
		return false;
	}
	entry->buffer.data = new byte[dwSize];
	entry->buffer.size = dwSize;
	if(!ReadFile(hFile, entry->buffer.data, dwSize, &dwSize, NULL))
	{
		entry->buffer.release();
		SAFE_DELETE(entry);
		CloseHandle(hFile);
		return false;
	}
	CloseHandle(hFile);

	pthread_rwlock_wrlock(&m_rwlock);
	if(checkHash && !MD5HashHelper::CheckDataBufferHash(entry->buffer, name.c_str()))
	{
		entry->buffer.release();
		SAFE_DELETE(entry);
		pthread_rwlock_unlock(&m_rwlock);
		return false;
	}

	m_cacheMap[name] = entry; 
	m_currentCacheSize += dwSize;
	pthread_rwlock_unlock(&m_rwlock);
	return true;
}

SVR_PROXY_NAMESPACE_END
